using System;
using System.Collections.Generic;
using System.Text;
using EnvDTE;
using System.IO;
using EnvDTE80;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;

namespace NUnitVSAddin
{
    /// <summary>
    /// Program Type
    /// </summary>
    public enum NUnitProgramType
    {
        Console,
        GUI
    }

    /// <summary>
    /// Nunit class to launch Process
    /// </summary>
    public class NUnit
    {
        private Connect _connect;

        /// <summary>
        /// Initializes a new instance of the <see cref="NUnit"/> class.
        /// </summary>
        /// <param name="connect">The connect.</param>
        public NUnit(Connect connect)
        {
            _connect = connect;
        }

        /// <summary>
        /// Launches the NUnit.
        /// </summary>
        /// <param name="programType">The program.</param>
        public void LaunchNUnit(NUnitProgramType programType)
        {
            string projectOutputFile = null;

            try
            {
                Project project = Common.GetStartupProject(_connect.ApplicationObject.Solution);
                if (project == null)
                {
                    _connect.DisplayErrorMessage("Error", "Open your test project first !");
                    return;
                }
                string outputDirectory = "";
                projectOutputFile = Common.GetProjectOutputFile(project, out outputDirectory);

                _connect.ApplicationObject.Solution.SolutionBuild.Clean(true);
                _connect.ApplicationObject.Solution.SolutionBuild.Build(true);

                if (!File.Exists(projectOutputFile))
                {
                    _connect.DisplayErrorMessage("Error", "Build Failed.");
                    return;
                }
            }
            catch (Exception ex)
            {
                _connect.DisplayErrorMessage("Startup project", "Failed to locate the output of the startup project.\n" + ex.ToString());
                return;
            }

            string processToLaunch = LocateLaunchProgram(programType);

            if (string.IsNullOrEmpty(processToLaunch))
                return; // Error is handled inside LocateLaunchProgram

            try
            {
                Win32Process.ProcessInformation pi = StartProcess(processToLaunch, "\"" + projectOutputFile + "\"");
                //    System.Threading.Thread.Sleep(10000);

                //_connect.ApplicationObject.Solution.SolutionBuild.Build(true);
                this.AttachProcess(pi.ProcessId, pi.ThreadHandle);
            }
            catch (Exception ex)
            {
                _connect.DisplayErrorMessage("Failed starting process.", ex.ToString());
            }
        }

        /// <summary>
        /// To attach Debug process
        /// </summary>
        /// <param name="execPath">Execute Path</param>
        /// <param name="arguments">arguments</param>
        private Win32Process.ProcessInformation StartProcess(string execPath, string arguments)
        {
            Win32Process.ProcessInformation pi = Win32Process.CreateProcess(execPath, arguments, null, Win32Process.CreationFlags.CREATE_SUSPENDED);

            if (pi.ProcessId == 0)
            {
                throw (new Exception("Failed starting the process \"" + execPath + "\"."));
            }
            return pi;
        }

        public void AttachProcess(uint processId, IntPtr handle)
        {
            try
            {
                Debugger2 debugger = (Debugger2)_connect.ApplicationObject.Debugger;
                Transport transport = debugger.Transports.Item("Default");

                Processes procList = debugger.GetProcesses(transport, ".");

                Process2 p2 = null;

                for (int i = 1; i <= procList.Count; i++)
                {
                    if (procList.Item(i).ProcessID == processId)
                    {
                        p2 = (Process2)procList.Item(i);
                        break;
                    }
                }

                if (p2 == null)
                    throw (new Exception("Unable to locate process with id " + processId));

                Engine[] engines = new Engine[1];
                engines[0] = transport.Engines.Item("Managed");

                p2.Attach2(engines);
            }
            catch (Exception ex)
            {
                throw (new Exception("Error occured while attaching debugger to the process.", ex));
            }
            finally
            {
                Win32Process.ResumeThread(handle);
            }
        }

        /// <summary>
        /// To locate the exec path from registry or settings file.
        /// </summary>
        /// <param name="programType"></param>
        /// <returns></returns>
        private string LocateLaunchProgram(NUnitProgramType programType)
        {
            string processToLaunch = null;
            string executableName = null;

            switch (programType)
            {
                case NUnitProgramType.Console:
                    processToLaunch = Settings.Default.NUnitConsolePath;
                    executableName = Resources.NUnitConsoleExec;
                    break;

                case NUnitProgramType.GUI:
                    processToLaunch = Settings.Default.NUnitGUIPath;
                    executableName = Resources.NUnitGUIExec;
                    break;
            }

            try
            {
                if (string.IsNullOrEmpty(processToLaunch))
                {
                    processToLaunch = Common.GetNUnitExecPath(executableName);

                    if (string.IsNullOrEmpty(processToLaunch))
                    {
                        _connect.DisplayErrorMessage("Failed to locate NUnit", "Make sure NUnit is installed and/or the location is correct in the settings of NUnitit.");
                        _connect.DisplaySettings();
                        return null;
                    }
                }
            }
            catch (Exception ex)
            {
                _connect.DisplayErrorMessage("Error locating NUnit", ex.ToString());
                return null;
            }

            if (!string.IsNullOrEmpty(processToLaunch) &&
               !File.Exists(processToLaunch))
            {
                _connect.DisplayErrorMessage("File not found", "Unable to open the executable \"" + processToLaunch + "\"");
                return null;
            }

            return processToLaunch;
        }
    }
}
